/**
 * TextArea is an editable text field component that supports multi-line text and an optional ScrollBar. It is derived from the CLIK TextInput component and thus includes all of the functionality and properties of TextInput. TextArea also shares the same states as its parent component.

	<b>Inspectable Properties</b>
	The inspectable properties of the TextArea component are similar to TextInput with a couple of additions and the omission of the password property. The additions are related to the CLIK ScrollBar component:<ul>
	<li><i>text</i>: Sets the text of the textField.</li>
	<li><i>visible</i>: Hides the component if set to false.</li>
	<li><i>disabled</i>: Disables the component if set to true.</li>
	<li><i>editable</i>: Makes the TextInput non-editable if set to false.</li>
	<li><i>maxChars</i>: A number greater than zero limits the number of characters that can be entered in the textField.</li>
	<li><i>scrollBar</i>: Instance name of the CLIK ScrollBar component to use, or a linkage ID to the ScrollBar symbol – an instance will be created by the TextArea in this case.</li>
	<li><i>scrollPolicy</i>: When set to “auto” the scrollBar will only show if there is enough text to scroll. The ScrollBar will always display if set to “on”, and never display if set to “off”. This property only affects the component if a ScrollBar is assigned to it (see the scrollBar property).</li>
	<li><i>defaultText</i>: Text to display when the textField is empty. This text is formatted by the defaultTextFormat object, which is by default set to light gray and italics.</li>
	<li><i>actAsButton</i>: If true, then the TextArea will behave similar to a Button when not focused and support rollOver and rollOut states. Once focused via mouse press or tab, the TextArea reverts to its normal mode until focus is lost.</li>
	<li><i>enableInitCallback</i>: If set to true, _global.CLIK_loadCallback() will be fired when a component is loaded and _global.CLIK_unloadCallback will be called when the component is unloaded. These methods receive the instance name, target path, and a reference the component as parameters.  _global.CLIK_loadCallback and _global.CLIK_unloadCallback should be overridden from the game engine using GFx FunctionObjects.</li>
	<li><i>soundMap</i>: Mapping between events and sound process. When an event is fired, the associated sound process will be fired via _global.gfxProcessSound, which should be overridden from the game engine using GFx FunctionObjects.</li></ul>

	<b>States</b>
	Like its parent, TextInput, the TextArea component supports three states based on its focused and disabled properties.<ul>
	<li>default or enabled state.</li>
	<li>focused state, typically a represented by a highlighted border around the textField.</li>
	<li>disabled state.</li></ul>

	<b>Events</b>
	All event callbacks receive a single Object parameter that contains relevant information about the event. The following properties are common to all events. <ul>
	<li><i>type</i>: The event type.</li>
	<li><i>target</i>: The target that generated the event.</li></ul>

	The events generated by the TextArea component are listed below. The properties listed next to the event are provided in addition to the common properties.<ul>
	<li><i>show</i>: The component’s visible property has been set to true at runtime.</li>
	<li><i>hide</i>: The component’s visible property has been set to false at runtime.</li>
	<li><i>focusIn</i>: The component has received focus.</li>
	<li><i>focusOut</i>: The component has lost focus.</li>
	<li><i>textChange</i>: The text field contents have changed.</li>
	<li><i>scroll</i>: The text area has been scrolled.</li>
	<li><i>rollOver</i>: The mouse cursor has rolled over the component when not focused. Only fired when the actAsButton property is set.<ul>
		<li><i>controllerIdx</i>: The index of the mouse cursor used to generate the event (applicable only for multi-mouse-cursor environments). Number type. Values 0 to 3.</li></ul></li>
	<li><i>rollOut</i>: The mouse cursor has rolled out of the component when not focused. Only fired when the actAsButton property is set.<ul>
		<li><i>controllerIdx</i>: The index of the mouse cursor used to generate the event (applicable only for multi-mouse-cursor environments). Number type. Values 0 to 3.</</li></ul></li></ul>

 */


/*
	Scrolling has 2 different bugs in flash vs GFx
		1. Flash: Seems that textField doesn't throw all the scroll events it should, so the scrollBar gets a little behind when scrolling using keys. particularly if we hold the key down.
		2. GFx: Scrolling does 2-lines, but will not indicate the last line of the TextField, when there are odd-number of lines.

	TextArea sets its TextField as the scrollTarget of the ScrollBar, so it doesn't pass any values into the scrollBar.  Maybe we should handle it manually so we can use other components like StatusIndicator.
*/


import flash.geom.Rectangle;
import gfx.controls.TextInput;
import gfx.ui.InputDetails;
import gfx.ui.NavigationCode;


[InspectableList("disabled", "visible", "textID", "maxChars", /*"restrict",*/ "editable", "inspectableScrollBar", "scrollPolicy", "actAsButton", "defaultText", "enableInitCallback", "soundMap")]
class gfx.controls.TextArea extends TextInput
{
	/* PUBLIC VARIABLES */

	/** Mapping between events and sound process */
    [Inspectable(type="Object", defaultValue="theme:default,focusIn:focusIn,focusOut:focusOut,textChange:textChange,scroll:scroll")]
    public var soundMap: Object = { theme:"default", focusIn:"focusIn", focusOut:"focusOut", textChange:"textChange", scroll:"scroll" };


	/* PRIVATE VARIABLES */

	private var _scrollPolicy: String = "auto";
	private var _position: Number = 1;
	private var maxscroll: Number = 1;
	[Inspectable(name="scrollBar", defaultValue="", type="String")]
	private var inspectableScrollBar: Object;
	private var autoScrollBar: Boolean = false;
	private var resetScrollPosition: Boolean = false;


	/* STAGE ELEMENTS */

	private var _scrollBar: MovieClip;
	private var container: MovieClip;


	/* INITIALIZATION */

	/**
	 * The constructor is called when a TextArea or a sub-class of TextArea is instantiated on stage or by using {@code attachMovie()} in ActionScript. This component can <b>not</b> be instantiated using {@code new} syntax. When creating new components that extend TextArea, ensure that a {@code super()} call is made first in the constructor.
	 */
	public function TextArea()
	{
		super();
	}

	/* PUBLIC FUNCTIONS */

	/**
	 * The vertical scroll position of the text.
	 */
	public function get position(): Number
	{
		return _position;
	}


	public function set position(value: Number)
	{
		_position = value;
		textField.scroll = _position;
	}


	/**
	 * Determines when the component displays a scrollBar. When the scrollPolicy is "auto" the scrollBar will only show if there is enough text to scroll. When it is "on", it will always display, and when it is "off", it will never display.
	 */
	[Inspectable(name="scrollPolicy", defaultValue="auto", type="list", enumeration="auto,on,off")]
	public function get scrollPolicy(): String
	{
		return _scrollPolicy;
	}


	public function set scrollPolicy(value: String): Void
	{
		_scrollPolicy = value;
		updateScrollBar();
	}


	/**
	 * The component to use as a scrollBar.  The ScrollBar can be defined using a component instance in the {@code _parent} scope, or by passing a linkage name to a ScrollBar symbol in the library. Unlike other scrolling components, only ScrollBars (including ScrollIndicator) can be used to scroll the TextArea.
	 */
	public function get scrollBar(): Object
	{
		return _scrollBar;
	}


	public function set scrollBar(value: Object): Void
	{
		if (!initialized) {
			inspectableScrollBar = value;
			return;
		}

		if (_scrollBar == value) {
			return;
		}

		if (_scrollBar != null) {
			_scrollBar.scrollTarget = null;
			_scrollBar.focusTarget = null;
			_scrollBar.removeEventListener("scroll", this, "handleScroll");
			if (autoScrollBar) {
				_scrollBar.removeMovieClip();
			}
		}

		autoScrollBar = false;
		if (typeof(value) == "string") {
			_scrollBar = MovieClip(_parent[value.toString()]);
			if (_scrollBar == null) {
				_scrollBar = container.attachMovie(value.toString(), "_scrollBar", 1000, {_visible: false}); // Don't use the "visible" property. Use the "_visible" instead.
				if (_scrollBar != null) {
					autoScrollBar = true;
				}
			}
		} else {
			_scrollBar = MovieClip(value);
		}

		if (_scrollBar == null) {
			return;
		}

		_scrollBar.focusTarget = this;
		_scrollBar.scrollTarget = textField;
		maxscroll = textField.maxscroll;
		updateScrollBar();
		changeLock = true;
		onChanged();
		changeLock = false;
	}


	/**
	 * Disable this component. Focus (along with keyboard events) and mouse events will be suppressed if disabled.
	 */
	[Inspectable(defaultValue="false", verbose="1")]
	public function get disabled(): Boolean
	{
		return _disabled;
	}


	public function set disabled(value: Boolean): Void
	{
		super.disabled = value;
		updateScrollBar();
	}


	/** @exclude */
	public function toString(): String
	{
		return "[Scaleform TextArea " + _name + "]";
	}


	public function handleInput(details: InputDetails, pathToFocus: Array): Boolean
	{
		if (details.value != "keyDown" && details.value != "keyHold") {
			return false;
		}

		var controllerIdx: Number = details.controllerIdx;
		if (Selection.getFocus(controllerIdx) == null) {
			Selection.setFocus(textField, controllerIdx);
			return true;
		}

		if (_editable) {
			return false;
		}

		switch(details.navEquivalent) {
			case NavigationCode.UP:
				if (position == 1) {
					return false;
				}
				position = Math.max(1, position - 1);
				return true;

			case NavigationCode.DOWN:
				if (position == maxscroll) {
					return false;
				}
				position = Math.min(maxscroll, position+1);
				return true;

			case NavigationCode.END:
				position = maxscroll;
				return true;

			case NavigationCode.HOME:
				position = 1;
				return true;

			case NavigationCode.PAGE_UP:
				var pageSize: Number = textField.bottomScroll - textField.scroll;
				position = Math.max(1, position-pageSize);
				return true;

			case NavigationCode.PAGE_DOWN:
				var pageSize: Number = textField.bottomScroll - textField.scroll;
				position = Math.min(maxscroll, position+pageSize);
				return true;
		}
		return false;
	}


	/* PRIVATE FUNCTIONS */

	private function configUI(): Void
	{
		super.configUI();
		Mouse.addListener(this);

		container = createEmptyMovieClip("container", 1); // This is where we can store a ScrollBar if it is automatically created.
		container.scale9Grid = new Rectangle(20, 20, 1, 1); // Note that Scrollbars or other sub-content should have a scale9 grid, even if it is not applied (ie, ScrollBar)

		if (inspectableScrollBar != "") {
			scrollBar = inspectableScrollBar;
			inspectableScrollBar = null;
		}
	}


	private function draw(): Void
	{
		super.draw();

		container._xscale = 10000 / _xscale; // Counter scale the list items.
		container._yscale = 10000 / _yscale;
		if (autoScrollBar) {
			_scrollBar._x = __width - _scrollBar._width; // Using the _width throughout to avoid initialization issues.
			_scrollBar.height = __height - 1;
		}
	}


	// Update the textField content
	private function updateText(): Void
	{
		super.updateText();
		updateScrollBar();
	}


	private function updateTextField(): Void
	{
		resetScrollPosition = true;
		super.updateTextField();
		if (textField != null) {
			if (_scrollBar != null) {
				_scrollBar.scrollTarget = textField;
			}
		}
	}


	// Update the scrollbar when the parameters change.
	private function updateScrollBar(): Void
	{
		maxscroll = textField.maxscroll;
		if (_scrollBar == undefined) {
			return;
		}

		var element: Object = constraints.getElement(textField);
		if (_scrollPolicy == "on" || (_scrollPolicy == "auto" && textField.maxscroll > 1)) {
			if (autoScrollBar && !_scrollBar.visible) { // Add some space on the right for the scrollBar
				if (element != null) {
					element.metrics.right += _scrollBar._width;
					constraints.update(__width, __height);
				}
				maxscroll = textField.maxscroll; // Set this again, in case adding a scrollBar made the maxScroll larger.
			}
			_scrollBar.visible = true;
		}

		if (_scrollPolicy == "off" || (_scrollPolicy == "auto" && textField.maxscroll == 1)) {
			if (autoScrollBar && _scrollBar.visible) {	// Remove any added space.
				if (element != null) {
					element.metrics.right -= _scrollBar._width;
					constraints.update(__width, __height);
				}
			}
			_scrollBar.visible = false;
		}

		if (_scrollBar.disabled != _disabled) {
			_scrollBar.disabled = _disabled;
		}
	}


	private function onChanged(target: Object): Void
	{
		if (maxscroll != textField.maxscroll) {
			updateScrollBar();
		}
		super.onChanged(target);
	}


	// The textField has scrolled, store the position for updating if the state changes.
	private function onScroller(): Void
	{
		if (resetScrollPosition) {
			textField.scroll = _position;
		} else {
			_position = textField.scroll;
		}
		resetScrollPosition = false;
		dispatchEventAndSound({type:"scroll"});
	}


	private function scrollWheel(delta: Number): Void
	{
		position = Math.max(1, Math.min(maxscroll, _position - delta));
	}
}
